#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>

#include <glib.h>
#include <libsoup/soup.h>

#include "http.h"
#include "util.h"
#include "jsonrpc_server.h"

typedef struct {
	json_object *mykit;
	json_object *mimetypes;
	const char *documentroot;
	const char *directoryindex;
	const char *name;
	int port;
} http_server;

http_server __server;
jsonrpc_server __jsonrpc_server;

static void http_server_free() {
	jsonrpc_destroy(&__jsonrpc_server);

	json_object_put(__server.mimetypes);
	json_object_put(__server.mykit);
}

static void http_server_init() {
	char *mimepath = g_build_filename(".", "conf", "mimetypes.json", NULL);
	char *mykitpath = g_build_filename(".", "conf", "mykit.json", NULL);
	__server.mimetypes = json_object_from_file(mimepath);
	__server.mykit = json_object_from_file(mykitpath);
	__server.documentroot = json_object_get_string(json_object_object_get(__server.mykit, "DocumentRoot"));
	__server.directoryindex = json_object_get_string(json_object_object_get(__server.mykit, "DirectoryIndex"));
	__server.port = json_object_get_int(json_object_object_get(__server.mykit, "Port"));
	__server.name = json_object_get_string(json_object_object_get(__server.mykit, "ServerName"));

	jsonrpc_server_init(&__jsonrpc_server);

	signal(SIGQUIT, http_server_free);
	signal(SIGINT, http_server_free); /* Ctrl + C */

	g_free(mimepath);
	g_free(mykitpath);
}

static const char *mime_type(const char *ext) {
	const char *mimetype = NULL;

	json_object_object_foreach(__server.mimetypes, key, val) {
		if (g_str_has_suffix(ext, key)) {
			mimetype = json_object_get_string(val);
		}
	}

	if (!mimetype) {
		mimetype = json_object_get_string(json_object_object_get(__server.mimetypes, "*"));
	}

	return mimetype;
}

static int http_ext(SoupMessage *msg) {
	int ret = 0;

	if (msg->method == SOUP_METHOD_POST) {
		const char *cmd = soup_message_headers_get(msg->request_headers, "__command__");
		if (cmd) {
			json_object *obj = jsonrpc_invoke(&__jsonrpc_server, msg->request_body->data);
			if (obj) {
				const char *body = json_object_to_json_string(obj);
				soup_message_body_append(msg->response_body, SOUP_MEMORY_COPY, body, strlen(body));
				json_object_put(obj);
			}
			soup_message_headers_append(msg->response_headers, HTTP_HEADER_CONTENT_TYPE, "application/json");

			ret = 1;
		}
	} else if (msg->method == SOUP_METHOD_GET) {
		SoupURI *uri = soup_message_get_uri(msg);
		if (uri->query) {
			GHashTable *query = soup_form_decode_urlencoded(uri->query);

			char *cmd = g_hash_table_lookup(query, "__command__");
			if (cmd) {
				if (g_strcasecmp(cmd, "download") == 0) {
					char *filename = g_hash_table_lookup(query, "file");
					GMappedFile *mapping = g_mapped_file_new(filename, FALSE, NULL);
					SoupBuffer *buffer = soup_buffer_new_with_owner(g_mapped_file_get_contents(mapping), g_mapped_file_get_length(mapping), mapping,
							(GDestroyNotify) g_mapped_file_unref);
					soup_message_body_append_buffer(msg->response_body, buffer);

					char *disposition = g_strdup_printf("attachment; filename=%s", strchr(filename, '\\') + 1);
					soup_message_headers_append(msg->response_headers, HTTP_HEADER_CONTENT_DISPOSITION, disposition);
					soup_message_headers_append(msg->response_headers, HTTP_HEADER_PRAGMA, "No-cache");
					soup_message_headers_append(msg->response_headers, HTTP_HEADER_CACHE_CONTROL, "No-cache");
					soup_message_headers_append(msg->response_headers, HTTP_HEADER_EXPIRES, "0");
					soup_message_headers_append(msg->response_headers, HTTP_HEADER_CONTENT_TYPE, "application/octet-stream");
					soup_buffer_free(buffer);

					g_free(disposition);

					ret = 1;
				} else if (g_strcasecmp(cmd, "file") == 0) {
					char *filename = g_hash_table_lookup(query, "name");
					GMappedFile *mapping = g_mapped_file_new(filename, FALSE, NULL);
					SoupBuffer *buffer = soup_buffer_new_with_owner(g_mapped_file_get_contents(mapping), g_mapped_file_get_length(mapping), mapping,
							(GDestroyNotify) g_mapped_file_unref);
					soup_message_body_append_buffer(msg->response_body, buffer);

					ret = 1;
				}
			}
			g_hash_table_destroy(query);
		}
	}

	if (ret == 1) {
		soup_message_set_status(msg, SOUP_STATUS_OK);
	}

	return ret;
}

static void http_get(SoupServer *server, SoupMessage *msg, const char *path) {
	GMappedFile *mapping;
	SoupBuffer *buffer;

	mapping = g_mapped_file_new(path, FALSE, NULL);
	if (!mapping) {
		soup_message_set_status(msg, SOUP_STATUS_INTERNAL_SERVER_ERROR);
		return;
	}

	buffer = soup_buffer_new_with_owner(g_mapped_file_get_contents(mapping), g_mapped_file_get_length(mapping), mapping, (GDestroyNotify) g_mapped_file_unref);
	soup_message_body_append_buffer(msg->response_body, buffer);
	soup_message_headers_append(msg->response_headers, HTTP_HEADER_CONTENT_TYPE, mime_type(path));

	soup_buffer_free(buffer);

	soup_message_set_status(msg, SOUP_STATUS_OK);
}

static void http_post(SoupServer *server, SoupMessage *msg, const char *path) {
	soup_message_set_status(msg, SOUP_STATUS_OK);
}

void http_print(SoupMessage *msg, const char *path) {
	SoupMessageHeadersIter iter;
	const char *name, *value;
	g_print("%s %s HTTP/1.%d\n", msg->method, path, soup_message_get_http_version(msg));
	soup_message_headers_iter_init(&iter, msg->request_headers);
	while (soup_message_headers_iter_next(&iter, &name, &value))
		g_print("%s: %s\n", name, value);
	if (msg->request_body->length)
		g_print("%s\n", msg->request_body->data);
}

static void server_callback(SoupServer *server, SoupMessage *msg, const char *path, GHashTable *query, SoupClientContext *context, gpointer data) {
	char *filename = g_build_filename((char *) data, (strcmp(path, "/") == 0) ? __server.directoryindex : path, NULL);

	/*http_print(msg, path);*/
	if (!http_ext(msg)) {
		if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
			if (msg->method == SOUP_METHOD_GET) {
				http_get(server, msg, filename);
			} else if (msg->method == SOUP_METHOD_POST) {
				http_post(server, msg, filename);
			} else {
				soup_message_set_status(msg, SOUP_STATUS_NOT_IMPLEMENTED);
			}
		} else {
			soup_message_set_status(msg, SOUP_STATUS_NOT_FOUND);
		}
	}
	g_free(filename);
}

void http_start() {
	http_server_init();

	SoupServer *server = soup_server_new(SOUP_SERVER_PORT, __server.port, SOUP_SERVER_SERVER_HEADER, __server.name, NULL);
	soup_server_add_handler(server, NULL, server_callback, (gpointer) __server.documentroot, NULL);
	soup_server_run_async(server);
}
